package com.bestcem.xm.common.core.uitls;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ReflectUtil;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.google.protobuf.*;
import com.google.protobuf.util.JsonFormat;
import com.google.protobuf.util.Timestamps;
import io.grpc.Grpc;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.*;

/**
 * gRpc 工具类
 * 最后更新时间: 2021年6月4日15:05:55
 *
 * @author XiaoYunSong <ys.xiao@idiaoyan.cn>
 * @version v1.0
 * @date 2021年5月8日11:45:04
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public final class GRpcUtil {
    /**
     * java 时间转 protobuf 时间统一偏移量
     */
    public static final String OUTPUT_TIME_OFFSET = "+0";
    private static final String REMOTE_IP_HEADER = "X-Real-Ip";

    private static final Map<String, Object> DEFAULT_VALUES = new HashMap<>();

    /**
     * 获取 Nginx 代理 Header 中的信息，转发的原始ip地址
     */
    private static final Metadata.Key<String> X_REAL_IP =
            Metadata.Key.of(REMOTE_IP_HEADER, Metadata.ASCII_STRING_MARSHALLER);

    static {
        DEFAULT_VALUES.put(int.class.getName(), 0);
        DEFAULT_VALUES.put(long.class.getName(), 0);
        DEFAULT_VALUES.put(float.class.getName(), 0);
        DEFAULT_VALUES.put(double.class.getName(), 0);
        DEFAULT_VALUES.put(boolean.class.getName(), false);
        DEFAULT_VALUES.put(String.class.getName(), "");
        DEFAULT_VALUES.put(Integer.class.getName(), 0);
        DEFAULT_VALUES.put(Long.class.getName(), 0);
        DEFAULT_VALUES.put(Float.class.getName(), 0);
        DEFAULT_VALUES.put(Double.class.getName(), 0);
        DEFAULT_VALUES.put(Boolean.class.getName(), Boolean.FALSE);
        DEFAULT_VALUES.put(List.class.getName(), Collections.emptyList());
    }

    public static <ReqT, RespT> String getRemoteIp(ServerCall<ReqT, RespT> call, Metadata headers) {
        // 获取 Nginx 代理 转发的原始ip地址
        String remoteIp = headers.get(X_REAL_IP);
        if (remoteIp == null || remoteIp.isEmpty()) {
            String inetSocketString = Objects.requireNonNull(call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR)).toString();
            remoteIp = CommonUtil.getIpv4(inetSocketString);
        }
        if (log.isInfoEnabled()) {
            log.info("remoteIp: {} headers: {} methodDescriptor: {}", remoteIp, headers, call.getMethodDescriptor());
        }
        return remoteIp;
    }


    /**
     * java.util.Date 转换成 protobuf.Timestamp
     *
     * @param date date
     * @return A Timestamp from the number of seconds elapsed from the epoch.
     * @author XiaoYunSong <ys.xiao@idiaoyan.cn>
     * @date 2021/5/12 18:07
     * @deprecated 对应方法见 {@link DateUtil#getTimestamp(Date)}
     */
    @Deprecated
    public static Timestamp getTimestamp(Date date) {
        return Timestamps.fromMillis(date.getTime());
    }

    /**
     * LocalDateTime 转换成 protobuf.Timestamp
     *
     * @param localDateTime dataTime
     * @return A Timestamp from the number of seconds elapsed from the epoch.
     * @author XiaoYunSong <ys.xiao@idiaoyan.cn>
     * @date 2021/5/12 18:07
     * @deprecated 对应方法见 {@link DateUtil#getTimestamp(LocalDateTime)}
     */
    @Deprecated
    public static Timestamp getTimestamp(LocalDateTime localDateTime) {
        return Timestamps.fromSeconds(localDateTime.toEpochSecond(ZoneOffset.of(OUTPUT_TIME_OFFSET)));
    }

    /**
     * protobuf.Timestamp 转为 LocalDateTime
     *
     * @param timestamp protobuf.timestamp
     * @return LocalDateTime
     * @author jy.zhao
     * @date 2021/5/11 10:50
     * @deprecated 对应方法见 {@link DateUtil#getLocalDateTime(Timestamp)}
     */
    @Deprecated
    public static LocalDateTime getLocalDateTime(Timestamp timestamp) {
        return LocalDateTimeUtil.ofUTC(Timestamps.toMillis(timestamp));
    }

    /**
     * protobuf.Timestamp 转为 java.util.Date
     *
     * @param timestamp protobuf.timestamp
     * @return LocalDateTime
     * @author jy.zhao
     * @date 2021/5/11 10:50
     * @deprecated 对应方法见 {@link DateUtil#getDate(Timestamp)}
     */
    public static Date getDate(Timestamp timestamp) {
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTimeUtil.ofUTC(Timestamps.toMillis(timestamp));
        ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
        return Date.from(zonedDateTime.toInstant());
    }


    /**
     * 使用 Validation 检验参数正确性
     *
     * @param param 接口输入参数
     * @return Verify result
     * @author XiaoYunSong <ys.xiao@idiaoyan.cn>
     * @date 2021/5/12 18:07
     */
    public static Set<ConstraintViolation<Object>> validateParam(Object param) {
        if (Objects.isNull(param)) {
            return Collections.emptySet();
        }
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        return validator.validate(param);
    }


    /**
     * 对象空属性填充默认值
     *
     * @param obj 目标对象
     * @author XiaoYunSong <ys.xiao@idiaoyan.cn>
     * @date 2021/5/12 18:07
     */
    public static <T> T fillDefaultValue(T obj) {
        if (obj == null) {
            return null;
        }

        Field[] fields = ReflectUtil.getFields(obj.getClass());
        for (Field field : fields) {
            Object value = ReflectUtil.getFieldValue(obj, field);
            if (value != null) {
                continue;
            }

            String type = field.getType().getName();
            value = DEFAULT_VALUES.get(type);
            if (value != null) {
                ReflectUtil.setFieldValue(obj, field, value);
            } else {
                if (LocalDateTime.class.getName().equals(type)) {
                    ReflectUtil.setFieldValue(obj, field, LocalDateTime.now());
                } else if (Date.class.getName().equals(type)) {
                    ReflectUtil.setFieldValue(obj, field, new Date());
                }
            }
        }
        return obj;
    }

    public static List<String> protocolStringToListString(ProtocolStringList list) {
        if (CollectionUtil.isNotEmpty(list)) {
            return new ArrayList<>(list);
        }
        return new ArrayList<>();
    }

    /**
     * 序列化
     */
    public static String serialize(MessageOrBuilder message) {
        try {
            return JsonFormat.printer().sortingMapKeys().preservingProtoFieldNames().print(message);
        } catch (InvalidProtocolBufferException e) {
            throw new BusinessException("serializable MessageOrBuilder failed", e);
        }

    }

    /**
     * 反序列化
     */
    public void deserialize(String json, Message.Builder builder) {
        try {
            JsonFormat.parser().merge(json, builder);
        } catch (InvalidProtocolBufferException e) {
            throw new BusinessException("deserializable Message.Builder failed", e);
        }
    }
}
